package enron;

import java.awt.EventQueue;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;

import enron.Generator.Generator;

import jade.core.AID;
import jade.core.Agent;
import jade.core.behaviours.TickerBehaviour;
import jade.domain.DFService;
import jade.domain.FIPAException;
import jade.domain.FIPANames;
import jade.domain.FIPAAgentManagement.DFAgentDescription;
import jade.domain.FIPAAgentManagement.ServiceDescription;
import jade.lang.acl.ACLMessage;
import jade.proto.SubscriptionInitiator;
import jade.wrapper.AgentContainer;
import jade.wrapper.AgentController;
import jade.wrapper.StaleProxyException;

public class EnergyGrid extends Agent {
	private static final long serialVersionUID = -6052752604331333767L;
	public static HashMap<String,Class<Generator>> generatorList;
	
	@SuppressWarnings("unused")
	private EnergyGridUI window;
	private Long load = 0L;
	private Long supply = 0L;
	private double frequency = 50.0;
	private double energyUsed = 0.0;
	private Long lastLoadUpdate;
	private Long lastSupplyUpdate;
	private double energySupplied = 0.0;
	boolean isFixingFrequency = false;
	private static ArrayList<AID> suppliers = new ArrayList<AID>();
	private static ArrayList<AID> users = new ArrayList<AID>();

	
	private class CheckGridFrequency extends TickerBehaviour {
		private static final long serialVersionUID = -879840561184272675L;

		public CheckGridFrequency(Agent a) {
			super(a, 1000);
		}

		@Override
		protected void onTick() {
			if(!((EnergyGrid)myAgent).isFixingFrequency && EnergyGrid.suppliers.size() > 0 && (load != supply) && (supply <= 0 || (Math.abs(load-supply)/supply.doubleValue()) > 0.2)) {
				((EnergyGrid)myAgent).fixFrequency();
			}
		}	
	}
	
	/* (non-Javadoc)
	 * @see jade.core.Agent#setup()
	 */
	@Override
	protected void setup() {
//		System.out.println("Initializing...");
		super.setup();
		deploySniffer();
		lastLoadUpdate = System.currentTimeMillis();
		lastSupplyUpdate = System.currentTimeMillis();
		window = new EnergyGridUI(this);
		this.addBehaviour(new CheckGridFrequency(this));
		supplierSubscription();
	}

	public EnergyGrid() {
		getGeneratorList();
	}

	public Long getLoad() {
		return load;
	}
	
	public void adjustLoad(Long adjustment) {
		updateEnergyUsed();
		this.load += adjustment;
		adjustFrequency();
//		System.out.println("Grid Load: " + load);
	}

	public Long getSupply() {
		return supply;
	}
	
	public void adjustSupply(Long adjustment) {
		updateEnergySupplied();
		this.supply += adjustment;
		adjustFrequency();
		//System.out.println("Adjusting by " + adjustment + " now at " + this.supply);
	}

	public double getFrequency() {
		return frequency;
	}

	protected void adjustFrequency() {
		frequency *= ((load-supply)/supply.doubleValue());
	}
	
	protected void fixFrequency() {
		isFixingFrequency = true;
//		System.out.println("Fixing...");
		ACLMessage msg = new ACLMessage(ACLMessage.CFP);
//		System.out.println("Adding Suppliers...");
  		for (AID vendor : suppliers) {
  			msg.addReceiver(vendor);
//  			System.out.println("Grid Adjust: "+ this.getLocalName() +" sending to " + vendor.getLocalName());
  		}
		msg.setProtocol(FIPANames.InteractionProtocol.FIPA_CONTRACT_NET);
		// We want to receive a reply in 10 secs
		msg.setReplyByDate(new Date(System.currentTimeMillis() + 10000));
		msg.setContent(Long.toString(load-supply));
//		System.out.println("Starting Adjust Behaviour for " + msg.getContent());
		addBehaviour(new AdjustSupplyBehaviour(this,msg,suppliers.size()));
	}
	

	public static ArrayList<AID> getSuppliers() {
		return suppliers;
	}
	
	public void supplierSubscription() {
		// Update the list of seller agents
	    DFAgentDescription template = new DFAgentDescription(); 
	    ServiceDescription templateSd = new ServiceDescription();
    
	    templateSd.setType("energy-vendor");
	    template.addServices(templateSd);

	    addBehaviour( new SubscriptionInitiator( this, DFService.createSubscriptionMessage( this, getDefaultDF(), template, null)) {
			private static final long serialVersionUID = -4448759199421243252L;

			protected void handleInform(ACLMessage inform) {
//				System.out.println("Vendor subscription: " + inform.getContent());
//				System.out.println(inform.getAllUserDefinedParameters());
				try {
					DFAgentDescription[] dfds = DFService.decodeNotification(inform.getContent());
					if(dfds.length > 0) {
						for(DFAgentDescription dfd : dfds) {
							if(!suppliers.contains(dfd.getName())) {
								suppliers.add(dfd.getName());
							} else if(!dfd.getAllServices().hasNext()) {
								suppliers.remove(dfd.getName());
							} else {
//								System.out.println("Shouldn't be here!");
							}
						}
					} else {
//						System.out.println("No Vendors...");
					}
				} catch (FIPAException fe) {
					fe.printStackTrace();
				}
			}
	    });
	}
	
	public static HashMap<String,Class<Generator>> getGeneratorList() {
		if(generatorList == null) {
			generatorList = new HashMap<String,Class<Generator>>();
			@SuppressWarnings("unchecked")
			ArrayList<Class<Generator>> generatorTypes = (ArrayList<Class<Generator>>)((Collection<?>)ClassEnumerator.getClassesForPackage(Generator.class.getPackage()));						
			
			for(Class<Generator> generatorType : generatorTypes) {
				if(generatorType.getSuperclass().equals(Generator.class)) {
					generatorList.put(generatorType.getSimpleName(), generatorType);
				} else {
					//System.out.println(generatorType.getName());
				}
			}
		}
		return generatorList;
	}

	public static ArrayList<AID> getUsers() {
		return users;
	}
	
	void deployAgent(String agentName, String agentClass, Object[] argsToPass) {
		AgentContainer containerController = this.getContainerController();
		try {
			AgentController agentController;
			agentController = containerController.createNewAgent(agentName, agentClass, argsToPass);
			agentController.start();
		} catch (Exception e){e.printStackTrace();}
	}
	
	/* (non-Javadoc)
	 * @see jade.core.Agent#takeDown()
	 */
	@Override
	protected void takeDown() {
		super.takeDown();
		EventQueue.invokeLater(new Runnable() {
			public void run() {
				try {
					try {
						getContainerController().kill();
					} catch (StaleProxyException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
	}
	
	private void deploySniffer() {
		AgentContainer agentContainer = null;

		while((agentContainer = this.getContainerController()) == null){
		}
		try {
			AgentController agentController;
			agentController = agentContainer.createNewAgent("Sniffer", "jade.tools.sniffer.Sniffer", new Object[]{"*"});
			agentController.start();
		} catch (Exception e){e.printStackTrace();}
	}
	
	public double getEnergyUsed() {
		return energyUsed;
	}

	public void setEnergyUsed(double energyUsed) {
		this.energyUsed = energyUsed;
	}

	private void updateEnergyUsed() {
		Long currentTime = System.currentTimeMillis();
		energyUsed += ((double)(currentTime-lastLoadUpdate)/3600000.0) * ((double)load/1000.0);
		lastLoadUpdate = currentTime;
	}
	
	public double getEnergySupplied() {
		return energySupplied;
	}

	public void setEnergySupplied(double energySupplied) {
		this.energySupplied = energySupplied;
	}

	private void updateEnergySupplied() {
		Long currentTime = System.currentTimeMillis();
		double newEnergy = ((double)(currentTime-lastSupplyUpdate)/3600000.0) * ((double)supply/1000.0);
		energySupplied += newEnergy;
		lastSupplyUpdate = currentTime;
	}

	public int getSupplierCount() {
		if(suppliers == null) {
			supplierSubscription();
		}
		return suppliers.size();
	}
}
